// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package fs

import (
	"path"
)

// GlobFS是一个带有Glob方法的文件系统。
type GlobFS interface {
	FS

	// Glob返回所有匹配模式文件的名称，
	// 提供顶级
	// Glob函数的实现。
	Glob(pattern string) ([]string, error)
}

// Glob返回与模式匹配的所有文件的名称，如果没有匹配的文件，则返回nil 
// 。模式的语法与path中的语法相同。火柴该模式可能描述分层名称，例如
// usr/*/bin/ed。
// 
// Glob忽略文件系统错误，例如读取目录时的I/O错误。
// 唯一可能返回的错误是path。ErrBadPattern，报告
// 模式格式不正确。
// 
// 如果fs实现了GlobFS，则Glob调用fs。地球仪。否则，Glob使用ReadDir遍历目录树，并查找模式的匹配项。
func Glob(fsys FS, pattern string) (matches []string, err error) {
	if fsys, ok := fsys.(GlobFS); ok {
		return fsys.Glob(pattern)
	}

	// 检查模式格式正确。
	if _, err := path.Match(pattern, ""); err != nil {
		return nil, err
	}
	if !hasMeta(pattern) {
		if _, err = Stat(fsys, pattern); err != nil {
			return nil, nil
		}
		return []string{pattern}, nil
	}

	dir, file := path.Split(pattern)
	dir = cleanGlobPath(dir)

	if !hasMeta(dir) {
		return glob(fsys, dir, file, nil)
	}

	// 防止无限递归。见第15879期。
	if dir == pattern {
		return nil, path.ErrBadPattern
	}

	var m []string
	m, err = Glob(fsys, dir)
	if err != nil {
		return
	}
	for _, d := range m {
		matches, err = glob(fsys, d, file, matches)
		if err != nil {
			return
		}
	}
	return
}

// cleanGlobPath为全局匹配准备路径。
func cleanGlobPath(path string) string {
	switch path {
	case "":
		return "."
	default:
		return path[0 : len(path)-1] // chop off training separator 
	}
}

// glob在目录dir 
// 中搜索匹配模式的文件，并将其附加到匹配项中，返回更新的片段。
// 如果目录无法打开，glob将返回现有的匹配项。
// 按字典顺序添加新匹配项。
func glob(fs FS, dir, pattern string, matches []string) (m []string, e error) {
	m = matches
	infos, err := ReadDir(fs, dir)
	if err != nil {
		return // ignore I/O error 
	}

	for _, info := range infos {
		n := info.Name()
		matched, err := path.Match(pattern, n)
		if err != nil {
			return m, err
		}
		if matched {
			m = append(m, path.Join(dir, n))
		}
	}
	return
}

// hasMeta报告path是否包含path识别的任何魔法字符
// 。火柴
func hasMeta(path string) bool {
	for i := 0; i < len(path); i++ {
		switch path[i] {
		case '*', '?', '[', '\\':
			return true
		}
	}
	return false
}
